/*
 * SoapUI, Copyright (C) 2004-2022 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui.impl.rest.panels.resource;

import com.eviware.soapui.impl.rest.support.RestParamProperty;
import com.eviware.soapui.impl.rest.support.RestParamsPropertyHolder;
import com.eviware.soapui.impl.rest.support.RestParamsPropertyHolder.ParameterStyle;
import com.eviware.soapui.impl.rest.support.RestUtils;
import com.eviware.soapui.impl.support.actions.ShowOnlineHelpAction;
import com.eviware.soapui.impl.wsdl.panels.teststeps.support.AddParamAction;
import com.eviware.soapui.impl.wsdl.panels.teststeps.support.MovePropertyDownAction;
import com.eviware.soapui.impl.wsdl.panels.teststeps.support.MovePropertyUpAction;
import com.eviware.soapui.impl.wsdl.panels.teststeps.support.RemovePropertyAction;
import com.eviware.soapui.impl.wsdl.support.HelpUrls;
import com.eviware.soapui.model.testsuite.TestProperty;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.components.JXToolBar;
import com.eviware.soapui.support.components.SimpleBindingForm;
import com.eviware.soapui.support.components.StringListFormComponent;
import com.eviware.soapui.support.swing.JTableFactory;
import com.jgoodies.binding.PresentationModel;
import org.apache.xmlbeans.SchemaType;
import org.apache.xmlbeans.XmlBeans;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.xml.namespace.QName;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;

import static com.eviware.soapui.impl.rest.actions.support.NewRestResourceActionBase.ParamLocation;


public class RestParamsTable extends JPanel {
    public static final String REST_PARAMS_TABLE = "RestParamsTable";
    public static final String REVERT_PARAMETER_VALUES = "Revert Parameter Values";
    protected RestParamsPropertyHolder params;
    protected RestParamsTableModel paramsTableModel;
    protected JTable paramsTable;
    protected AddParamAction addParamAction = null;
    protected RemovePropertyAction removeParamAction = null;
    protected UseDefaultParamsAction defaultParamsAction = null;
    protected MovePropertyDownAction movePropertyDownAction = null;
    protected MovePropertyUpAction movePropertyUpAction = null;
    protected UpdateParamsAction updateParamsAction = null;
    private PresentationModel<RestParamProperty> paramDetailsModel;
    private SimpleBindingForm detailsForm;
    private ParamLocation defaultParamLocation;
    private boolean showEditableButtons;
    private boolean showDefaultParamsButton;
    private JSplitPane splitPane;
    private JScrollPane scrollPane;

    public RestParamsTable(RestParamsPropertyHolder params, boolean showInspector, ParamLocation defaultParamLocation,
                           boolean showEditableButtons, boolean showDefaultParamsButton) {
        this(params, showInspector, new RestParamsTableModel(params, RestParamsTableModel.Mode.MEDIUM), defaultParamLocation, showEditableButtons,
                showDefaultParamsButton);
    }

    public RestParamsTable(RestParamsPropertyHolder params, boolean showInspector, RestParamsTableModel model,
                           ParamLocation defaultParamLocation, boolean showEditableButtons, boolean showDefaultParamsButton) {
        super(new BorderLayout());
        this.defaultParamLocation = defaultParamLocation;
        this.showEditableButtons = showEditableButtons;
        this.showDefaultParamsButton = showDefaultParamsButton;
        this.params = params;
        this.paramsTableModel = model;
        init(showInspector);
    }

    protected void init(boolean showInspector) {

        paramsTable = new JTable(paramsTableModel) {

            @Override
            public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
                Component defaultRenderer = super.prepareRenderer(renderer, row, column);
                if (UISupport.isMac()) {
                    JTableFactory.applyStripesToRenderer(row, defaultRenderer);
                }
                return defaultRenderer;
            }

            @Override
            public void removeEditor() {
                TableCellEditor editor = getCellEditor();
                // must be called here to remove the editor and to avoid an infinite
                // loop, because the table is an editor listener and the
                // editingCanceled method calls this removeEditor method
                super.removeEditor();
                if (editor != null) {
                    editor.cancelCellEditing();
                }
            }

            @Override
            public Component prepareEditor(TableCellEditor editor, int row, int column) {
                Component component = super.prepareEditor(editor, row, column);
                if (getColumnClass(column) == ParameterStyle.class) {
                    RestParamProperty parameter = paramsTableModel.getParameterAt(row);
                    JComboBox comboBox = (JComboBox) ((DefaultCellEditor) editor).getComponent();
                    comboBox.setModel(getStylesForLocation(parameter.getParamLocation()));
                    super.prepareEditor(editor, row, column);
                }
                if (getColumnClass(column) == ParamLocation.class) {
                    RestParamProperty parameter = paramsTableModel.getParameterAt(row);
                    JComboBox comboBox = (JComboBox) ((DefaultCellEditor) editor).getComponent();
                    comboBox.setModel(getLocationForParameter(parameter.getStyle()));
                    super.prepareEditor(editor, row, column);
                }
                return component;
            }
        };
        paramsTable.setName(REST_PARAMS_TABLE);
        paramsTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);

        if (showDefaultParamsButton) {
            defaultParamsAction = new UseDefaultParamsAction();
        }

        movePropertyDownAction = new MovePropertyDownAction(paramsTable, params, "Moves selected parameter down one row");
        movePropertyUpAction = new MovePropertyUpAction(paramsTable, params, "Moves selected parameter up one row");


        if (showEditableButtons) {
            initEditableButtons();
        }


        paramsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        paramsTable.setDefaultEditor(ParameterStyle.class, new DefaultCellEditor(
                new JComboBox(getStylesForLocation(ParamLocation.RESOURCE))));
        paramsTable.setDefaultEditor(ParamLocation.class, new DefaultCellEditor(
                new JComboBox(ParamLocation.values())));
        // Workaround: for some reason the lower part of text gets clipped on some platforms
        paramsTable.setRowHeight(25);
        paramsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {

            public void valueChanged(ListSelectionEvent e) {
                int selectedRow = paramsTable.getSelectedRow();
                if (showEditableButtons) {
                    removeParamAction.setEnabled(selectedRow != -1);
                }
                if (showDefaultParamsButton) {
                    defaultParamsAction.setEnabled(paramsTable.getRowCount() > 0);
                }
                movePropertyDownAction.setEnabled(selectedRow < paramsTable.getRowCount() - 1);
                movePropertyUpAction.setEnabled(selectedRow > 0);

                if (selectedRow != -1) {
                    RestParamProperty selectedParameter = getSelectedParameter();
                    if (paramDetailsModel != null) {
                        updateDetailsFormWith(selectedParameter);
                        detailsForm.setEnabled(true);
                    }
                } else {
                    if (paramDetailsModel != null) {
                        detailsForm.setEnabled(false);
                        updateDetailsFormWith(null);
                    }
                }
            }
        });

        add(buildToolbar(), BorderLayout.NORTH);

        scrollPane = new JScrollPane(paramsTable);

        if (showInspector) {
            splitPane = UISupport.createVerticalSplit(scrollPane, buildDetails());
            add(splitPane, BorderLayout.CENTER);
            splitPane.setResizeWeight(0.7);
        } else {
            add(scrollPane, BorderLayout.CENTER);
        }
    }

    private DefaultComboBoxModel getStylesForLocation(ParamLocation paramLocation) {
        if (paramLocation == ParamLocation.METHOD) {
            return new DefaultComboBoxModel(
                    new ParameterStyle[]{ParameterStyle.QUERY, ParameterStyle.HEADER, ParameterStyle.MATRIX, ParameterStyle.PLAIN});
        } else {
            return new DefaultComboBoxModel(
                    new ParameterStyle[]{ParameterStyle.QUERY, ParameterStyle.TEMPLATE, ParameterStyle.HEADER, ParameterStyle.MATRIX, ParameterStyle.PLAIN});
        }
    }

    private DefaultComboBoxModel getLocationForParameter(ParameterStyle style) {
        if (style != ParameterStyle.TEMPLATE) {
            return new DefaultComboBoxModel(
                    new ParamLocation[]{ParamLocation.RESOURCE, ParamLocation.METHOD});
        } else {
            return new DefaultComboBoxModel(
                    new ParamLocation[]{ParamLocation.RESOURCE});
        }
    }

    private void updateDetailsFormWith(RestParamProperty selectedParameter) {
        try {
            paramDetailsModel.setBean(selectedParameter);
        } catch (Exception e) {
            splitPane.setBottomComponent(buildDetails());
            paramDetailsModel.setBean(selectedParameter);
        }
    }

    @Override
    public synchronized void addKeyListener(KeyListener l) {
        super.addKeyListener(l);
        paramsTable.addKeyListener(l);
    }

    private void initEditableButtons() {
        addParamAction = new AddParamAction(paramsTable, params, "Adds a parameter to the parameter table");
        removeParamAction = new RemovePropertyAction(paramsTable, params, "Removes the selected parameter");
        updateParamsAction = new UpdateParamsAction();

    }

    private JComponent buildDetails() {
        paramDetailsModel = new PresentationModel<>(null);
        detailsForm = new SimpleBindingForm(paramDetailsModel);

        detailsForm.addSpace(5);
        detailsForm.appendCheckBox("required", "Required", "Sets if parameter is required");

        List<QName> types = new ArrayList<>();
        for (SchemaType type : XmlBeans.getBuiltinTypeSystem().globalTypes()) {
            types.add(type.getName());
        }

        detailsForm.appendComboBox("type", "Type", types.toArray(), "The type of the parameter");
        StringListFormComponent optionsFormComponent = new StringListFormComponent("Available values for this Parameter");
        //TODO: Consider removing hardcoded size
        optionsFormComponent.setPreferredSize(new Dimension(350, 100));
        detailsForm.appendComponent("options", "Options", optionsFormComponent);
        detailsForm.appendTextField("description", "Description", "A short description of the parameter");
        detailsForm.appendCheckBox("disableUrlEncoding", "Disable Encoding",
                "Disables URL-Encoding of the parameter value");

        detailsForm.addSpace(5);

        detailsForm.setEnabled(false);

        return new JScrollPane(detailsForm.getPanel());
    }

    protected RestParamProperty getSelectedParameter() {
        return paramsTable.getSelectedRow() == -1 ? null : paramsTableModel.getParameterAt(paramsTable.getSelectedRow());
    }

    public JTable getParamsTable() {
        return paramsTable;
    }

    public void release() {
        paramsTableModel.release();
        if (paramDetailsModel != null) {
            paramDetailsModel.setBean(null);
        }
    }

    protected Component buildToolbar() {
        JXToolBar toolbar = UISupport.createToolbar();

        //TODO: the action should be disabled by default if the corresponding component (button)
        // is supposed to be disabled by default.
        if (showEditableButtons) {
            toolbar.add(UISupport.createToolbarButton(addParamAction));
            toolbar.add(UISupport.createToolbarButton(removeParamAction, false));
            toolbar.addSeparator();
            toolbar.add(UISupport.createToolbarButton(updateParamsAction));
        }

        if (showDefaultParamsButton) {
            toolbar.add(UISupport.createToolbarButton(defaultParamsAction, paramsTable.getRowCount() > 0));
        }

        toolbar.addSeparator();
        if (!inMinimalMode()) {
            toolbar.add(UISupport.createToolbarButton(movePropertyDownAction, false));
            toolbar.add(UISupport.createToolbarButton(movePropertyUpAction, false));
        }
        toolbar.addSeparator();

        toolbar.addGlue();


        toolbar.add(UISupport.createToolbarButton(new ShowOnlineHelpAction(HelpUrls.HTTP_REQUEST_PARAMS_HELP_URL)));


        return toolbar;
    }

    private boolean inMinimalMode() {
        RestParamsTableModel tableModel = (RestParamsTableModel) getParamsTable().getModel();
        return tableModel.isInMinimalMode();
    }

    public void extractParams(RestParamsPropertyHolder params, ParamLocation location) {
        for (int i = 0; i < paramsTable.getRowCount(); i++) {
            RestParamProperty prop = paramsTableModel.getParameterAt(i);
            if (paramsTableModel.getParamLocationAt(i) == location) {
                params.addParameter(prop);
            }
        }
    }

    public void focusParameter(String parameterName) {
        paramsTable.grabFocus();
        for (int i = 0; i < paramsTable.getRowCount(); i++) {
            if (paramsTable.getValueAt(i, 0).equals(parameterName)) {
                paramsTable.setRowSelectionInterval(i, i);
                paramsTable.editCellAt(i, 1);
                JTextField editorComponent = (JTextField) paramsTable.getEditorComponent();
                editorComponent.grabFocus();
                editorComponent.selectAll();
                scrollIntoPosition(i, paramsTable.getRowCount());
                return;
            }
        }

    }

    private void scrollIntoPosition(int selectedIndex, int numOfIndices) {
        scrollIntoPosition((double) selectedIndex / numOfIndices);
    }

    private void scrollIntoPosition(double percent) {
        int maximumScrollPosition = scrollPane.getVerticalScrollBar().getMaximum();
        int requestedScrollPosition = (int) Math.round(maximumScrollPosition * percent);
        scrollPane.getVerticalScrollBar().setValue(requestedScrollPosition);
    }

    private class UpdateParamsAction extends AbstractAction {
        private UpdateParamsAction() {
            putValue(Action.SMALL_ICON, UISupport.createImageIcon("/update-request-parameters-from-url.png"));
            putValue(Action.SHORT_DESCRIPTION, "Updates params from a specified URL");
        }

        public void actionPerformed(ActionEvent e) {
            String str = UISupport.prompt("Enter new url below", "Extract Params", "");
            if (str == null) {
                return;
            }

            try {
                RestUtils.extractParams(str, params, false,
                        defaultParamLocation == ParamLocation.RESOURCE
                                ? RestUtils.TemplateExtractionOption.EXTRACT_TEMPLATE_PARAMETERS
                                : RestUtils.TemplateExtractionOption.IGNORE_TEMPLATE_PARAMETERS);
            } catch (Exception e1) {
                UISupport.showErrorMessage(e1);
            }
        }
    }

    private class UseDefaultParamsAction extends AbstractAction {
        public UseDefaultParamsAction() {
            super(REVERT_PARAMETER_VALUES);
            putValue(Action.SMALL_ICON, UISupport.createImageIcon("/default_properties.gif"));
            putValue(Action.SHORT_DESCRIPTION, "Reverts all current parameters to default values");
            setEnabled(false);
        }

        public void actionPerformed(ActionEvent e) {
            if (UISupport.confirm("Revert all parameters to default values?", "Use Defaults")) {
                for (TestProperty property : params.getProperties().values()) {
                    property.setValue(null);
                }
            }
        }
    }

    public void setParams(RestParamsPropertyHolder params) {
        this.params = params;
        paramsTableModel.setParams(params);
    }

    public void refresh() {
        paramsTableModel.fireTableDataChanged();
    }
}
